﻿/////////////////////////////////////////////////////////////////////////////////////////////////////////
//
//Copyright (c) 2009 foldier inc.
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
//associated documentation files (the "Software"), to deal in the Software without restriction, including 
//without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
//copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 
//following conditions:
//
//The above copyright notice and this permission notice shall be included in all copies or substantial portions
//of the Software.
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED 
//WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
//PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 
//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
//LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
//INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
//OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 
//IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Threading;
using System.Net;
using System.Collections.Generic;
using System.Text;
using System.IO;

#if SILVERLIGHT
using System.Windows.Browser;
#else
using System.Web;
#endif

namespace foldier.API
{
    public enum oAuthState
    {
        idle = 0,
        tokenRequested = 1,
        tokenAuthorized = 2,
        accessTokenGranted = 3
    }

	public class OauthRequestState
	{
		public HttpWebRequest m_request;
		public string m_result;
		public string m_error;
	}
    /// <summary>
    /// Class to manage OAuth authentication process
    /// </summary>
    public class FoldierOauth
    {
        protected string m_request_token_path = string.Empty;
        protected string m_access_token_path = string.Empty;
        protected string m_consumer_key = string.Empty;
        protected string m_consumer_secret = string.Empty;
        protected string m_signedRequestPath = string.Empty;

        public string RequestTokenPath { get { return m_request_token_path; } set { m_request_token_path = value; } }
        public string AccessTokenPath { get { return m_access_token_path; } set { m_access_token_path = value; } }
        public string ConsumerKey { get { return m_consumer_key; } }
        public string ConsumerSecret { get { return m_consumer_secret; } }
        public string SignedRequestPath { get { return m_signedRequestPath; } set { m_signedRequestPath = value; } }


		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="key">Consumer key</param>
		/// <param name="secret">Consumer secret key</param>
		public FoldierOauth( string key, string secret )
        {
            m_consumer_key = key;
            m_consumer_secret = secret;
        }

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="key">Consumer key</param>
		/// <param name="secret">Consumer secret key</param>
		/// <param name="request_token_path">Path for request token</param>
        /// <param name="access_token_path">Path for access token</param>
        /// <param name="signedRequestPath">Path for signed request</param>
		public FoldierOauth( string key, string secret, string request_token_path, string access_token_path, string signedRequestPath )
        {
            m_consumer_key = key;
            m_consumer_secret = secret;
            m_request_token_path = request_token_path;
            m_access_token_path = access_token_path;
            m_signedRequestPath = signedRequestPath;
        }

        /// <summary>
        /// Encodes the path portion of a URL string for reliable HTTP transmission from
        //     the Web server to a client.
        /// </summary>
        /// <param name="source">String to encode</param>
        /// <returns>string</returns>
		static public string UriEncode( string source )
		{
#if SILVERLIGHT
			return HttpUtility.UrlEncode( source );
#else
			return HttpUtility.UrlPathEncode(source);
#endif
		}

        /// <summary>
        /// Contact the provider for a request token.
        /// Note: the routine already makes the UrlEncoding when needed.
        /// </summary>
        /// <param name="url">An additional URL not included in RequestTokenPath, string.Empty if not needed</param>
        /// <param name="parameters">Additional parameters required by the provider in the form: par1=val1&par2=val2&...</param>
        /// <returns>Request token string</returns>
		/// ----------------------------------------------------------------------------------------------------------
		public string customerRequestToken( string url, string parameters )
        {
            OAuthBase oAuth = new OAuthBase();
            string url_full = string.IsNullOrEmpty(url) ? m_request_token_path : m_request_token_path.TrimEnd('/') + '/' + OAuthBase.UrlEncode(url);
            if (!Uri.IsWellFormedUriString(url_full, UriKind.Absolute))
                return string.Empty;

            Uri uri = new Uri(url_full);

            string nonce = oAuth.GenerateNonce();
            string timeStamp = oAuth.GenerateTimeStamp();
            string normUrl = string.Empty;
            string reqPar = string.Empty;
            string sig = oAuth.GenerateSignature(uri, m_consumer_key, m_consumer_secret, string.Empty, string.Empty, "GET", timeStamp, nonce, OAuthBase.SignatureTypes.HMACSHA1, parameters, out normUrl, out reqPar);

            Uri uri_full = new Uri(url_full + "?" + reqPar + "&oauth_signature=" + OAuthBase.UrlEncode(sig));
            return getCommand(uri_full, null);
        }


        /// <summary>
        /// Contact the provider for the definitive access token.
        /// Note: the routine already makes the UrlEncoding when needed.
        /// </summary>
        /// <param name="url">An additional URL not included in RequestTokenPath, string.Empty if not needed</param>
        /// <param name="parameters">Additional parameters required by the provider in the form: par1=val1&par2=val2&...</param>
        /// <param name="requestToken">The request token</param>
        /// <param name="requestTokenSecret">The request token secret</param>
        /// <returns>Access token string</returns>
		/// ----------------------------------------------------------------------------------------------------------
		public string getAccessToken( string url, string parameters, string requestToken, string requestTokenSecret )
        {
            OAuthBase oAuth = new OAuthBase();
            string url_full = string.IsNullOrEmpty(url) ? m_access_token_path : m_access_token_path.TrimEnd('/') + '/' + OAuthBase.UrlEncode(url);
            if (!Uri.IsWellFormedUriString(url_full, UriKind.Absolute))
                return string.Empty;

            Uri uri = new Uri(url_full);

            string nonce = oAuth.GenerateNonce();
            string timeStamp = oAuth.GenerateTimeStamp();
            string normUrl = string.Empty;
            string reqPar = string.Empty;
            string sig = oAuth.GenerateSignature(uri, m_consumer_key, m_consumer_secret, HttpUtility.UrlDecode(requestToken), HttpUtility.UrlDecode(requestTokenSecret), "GET", timeStamp, nonce, OAuthBase.SignatureTypes.HMACSHA1, parameters, out normUrl, out reqPar);

            Uri uri_full = new Uri(url_full + "?" + reqPar + "&oauth_signature=" + OAuthBase.UrlEncode(sig));
            return getCommand(uri_full, null);
        }


        /// <summary>
        /// Send an authorized request to the provider.
        /// Note. the routine already makes the UrlEncoding when needed.
        /// </summary>
        /// <param name="url">An additional URL not included in RequestTokenPath, string.Empty if not needed</param>
        /// <param name="parameters">Additional parameters required by the provider in the form: par1=val1&par2=val2&...</param>
        /// <param name="extraHeaders">Additional headers to send in the request, "null" if not needed</param>
        /// <param name="accessToken">The access token</param>
        /// <param name="accessTokenSecret">The access token secret</param>
        /// <returns>Http response string</returns>
		/// ----------------------------------------------------------------------------------------------------------
		public string sendSignedRequest( string url, string parameters, Dictionary<string, string> extraHeaders, string accessToken, string accessTokenSecret )
        {
            OAuthBase oAuth = new OAuthBase();

            string url_full = string.IsNullOrEmpty(url) ? m_signedRequestPath : m_signedRequestPath.TrimEnd('/') + '/' + OAuthBase.UrlEncode(url);
            if (!Uri.IsWellFormedUriString(url_full, UriKind.Absolute))
                return string.Empty;

            Uri uri = new Uri(url_full);
            string nonce = oAuth.GenerateNonce();
            string timeStamp = oAuth.GenerateTimeStamp();
            string normUrl = string.Empty;
            string reqPar = string.Empty;
            string sig = oAuth.GenerateSignature(uri, m_consumer_key, m_consumer_secret, HttpUtility.UrlDecode(accessToken), HttpUtility.UrlDecode(accessTokenSecret), "GET", timeStamp, nonce, OAuthBase.SignatureTypes.HMACSHA1, parameters, out normUrl, out reqPar);

            Uri uri_full = new Uri(url_full + (string.IsNullOrEmpty(parameters) ? string.Empty : ("?" + parameters)));

            Dictionary<string, string> headers = new Dictionary<string, string>();

            if ((extraHeaders != null) && (extraHeaders.Count > 0))
                headers = extraHeaders;

            // Headers
            headers.Add(HttpRequestHeader.Authorization.ToString(), string.Format("OAuth oauth_version=\"1.0\", oauth_nonce=\"{0}\", oauth_timestamp=\"{1}\", oauth_consumer_key=\"{2}\", oauth_token=\"{3}\", oauth_signature_method=\"HMAC-SHA1\", oauth_signature=\"{4}\"",
                nonce, timeStamp, m_consumer_key, accessToken, OAuthBase.UrlEncode(sig)));

            string result = getCommand(uri_full, headers);
            headers.Remove(HttpRequestHeader.Authorization.ToString());

            return result;
        }


        /// <summary>
        /// Send an partially authorized request to the provider (no auth_token received). This is like e.g. Photobucket (url with encoding chars)
        /// Note: the routine already makes the UrlEncoding when needed.
        /// </summary>
        /// <param name="url">An additional URL not included in RequestTokenPath, string.Empty if not needed</param>
        /// <param name="parameters">Additional parameters required by the provider in the form: par1=val1&par2=val2&...</param>
        /// <returns>Http response string</returns>
		/// ----------------------------------------------------------------------------------------------------------
		public string sendPartiallySignedRequest( string url, string parameters )
        {
            OAuthBase oAuth = new OAuthBase();
            if (!Uri.IsWellFormedUriString(url, UriKind.RelativeOrAbsolute))
                return string.Empty;

            string nonce = oAuth.GenerateNonce();
            string timeStamp = oAuth.GenerateTimeStamp();
            string normUrl = string.Empty;
            string reqPar = string.Empty;
            string sig = oAuth.GenerateSignature(url, m_consumer_key, m_consumer_secret, string.Empty, string.Empty, "GET", timeStamp, nonce, OAuthBase.SignatureTypes.HMACSHA1, parameters, out normUrl, out reqPar);

            string url_full = url + "?" + reqPar + "&oauth_signature=" + OAuthBase.UrlEncode(sig);
            return getCommand(url_full, null);
        }

        /// <summary>
        ///     Performs a Http PUT on the given URI
        /// </summary>
        /// <param name="cmdUrl">URI string</param>
        /// <returns>Http response string</returns>
        /// ----------------------------------------------------------------------------------------------------------
        static public string putCommand(Uri cmdUrl, string message)
		{
#if SILVERLIGHT
			return string.Empty;
#else
            System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
            byte[] arr = encoding.GetBytes(message);
            string returnString = string.Empty;

            HttpWebRequest request = WebRequest.Create(cmdUrl) as HttpWebRequest;
            request.Method = "PUT";
            request.ContentType = "application/x-www-form-urlencoded";
            request.ContentLength = arr.Length;
            request.KeepAlive = true;
			try
            {
                Stream dataStream;
				dataStream = request.GetRequestStream();
                dataStream.Write(arr, 0, arr.Length);
                dataStream.Close();
                HttpWebResponse response = (HttpWebResponse)request.GetResponse();
                StreamReader readStream = new StreamReader(response.GetResponseStream());
                returnString = readStream.ReadToEnd();
            }
            catch
            {
                return string.Empty;
            }

            return returnString;
#endif
		}



        /// <summary>
        ///     Performs a http GET on the given URI
        /// </summary>
        /// <param name="cmdUrl">Url of the request</param>
        /// <param name="headers">All the required headers of the GET request; null if no headers are required</param>
        /// <returns>Http response string</returns>
        /// ----------------------------------------------------------------------------------------------------------
        static public string getCommand(Uri cmdUrl, Dictionary<string, string> headers)
        {
            HttpWebRequest request = WebRequest.Create(cmdUrl) as HttpWebRequest;
            return sendGetReq(request, headers);
        }


        /// <summary>
        ///     Performs a http GET on the given URI
        /// </summary>
        /// <param name="cmdUrl">Url of the request</param>
        /// <param name="headers">All the required headers of the GET request; null if no headers are required</param>
        /// <returns>Http response string</returns>
        /// ----------------------------------------------------------------------------------------------------------
        static public string getCommand(string cmdUrl, Dictionary<string, string> headers)
		{
            HttpWebRequest request = WebRequest.Create(cmdUrl) as HttpWebRequest;
            return sendGetReq(request, headers);
        }


#if SILVERLIGHT
		private static string sendGetReq( XMLHTTPRequest request, Dictionary<string, string> headers )
		{
			if( request == null )
				return string.Empty;

			// add headers of the request
			if( headers != null )
			{
				foreach( string key in headers.Keys )
				{
					request.Headers.Add( key, headers [key] );
				}
			}

			// Asyncronous call ( .NET Framework Class Library for Silverlight )
			// --------------------------------------------------------------------------------------------------------------
			OauthRequestState asyncReq = new OauthRequestState { m_request = request, m_result = string.Empty, m_error=string.Empty };
			IAsyncResult pResult = request.BeginGetResponse( new AsyncCallback( FoldierOauth.OnGetResponse ), asyncReq ); 
			Monitor.Wait( pResult.AsyncWaitHandle );
			return asyncReq.m_result;
		}

#else
        /// <summary>
        /// Utility function: makes a HTTP GET request
        /// </summary>
        /// <param name="request"></param>
        /// <param name="headers"></param>
        /// <returns></returns>
		/// ----------------------------------------------------------------------------------------------------------
		private static string sendGetReq(HttpWebRequest request, Dictionary<string, string> headers)
        {
            if (request == null)
                return string.Empty;

            // add headers of the request
            if (headers != null)
            {
                foreach (string key in headers.Keys)
                {
                    request.Headers.Add(key, headers[key]);
				}
            }

			// Syncronous call ( regular .NET )
			// --------------------------------------------------------------------------------------------------------------
            HttpWebResponse response;
            try
            {
                response = request.GetResponse() as HttpWebResponse;
			}
            catch
            {
                return string.Empty;
            }

            StreamReader readStream = new StreamReader(response.GetResponseStream());
            return readStream.ReadToEnd();
		}
#endif

#if SILVERLIGHT

		/// <summary>
		/// 	 Asyncronous responxe ( .NET Framework Class Library for Silverlight )
		/// </summary>
		/// <param name="asyncResult"></param>
		/// ------------------------------------------------------------------------------------------------------------------
        private static void OnGetResponse(IAsyncResult asyncResult)
        {
			OauthRequestState asyncReq = asyncResult.AsyncState as OauthRequestState;
			if( asyncReq == null )
			{
				Monitor.Pulse( asyncResult.AsyncWaitHandle );
				return;
			}
			try
			{
				HttpWebResponse response = ( HttpWebResponse ) asyncReq.m_request.EndGetResponse( asyncResult );
				StreamReader readStream = new StreamReader( response.GetResponseStream() );
				asyncReq.m_result = readStream.ReadToEnd();
			}
			catch( Exception exErr )
			{
				asyncReq.m_error = exErr.Message;
			}
			Monitor.Pulse( asyncResult.AsyncWaitHandle );

		}
#endif


	}
}

